﻿using System;
using System.Diagnostics;
using RaytracerLib.MathLib;
using TomanuExtensions;
using RaytracerLib.Rays;
using RaytracerLib.UVMappers;

namespace RaytracerLib.RenderableObjects
{
    /// <summary>
    /// Plane in local space lays in XZ plane.
    /// </summary>
    public class PlaneObject : RenderableObject
    {
        public PlaneObject(Vector3 a_normal, float a_dist, Vector3 a_right)
            : base(a_right, a_normal)
        {
            Name = "Plane";
            m_uv_mapper = new PlaneUVMapper();
            Pos = a_normal.Normalized * a_dist;
            Update(UpdateFlags.All);
        }

        public PlaneObject(Vector3 a_normal, Vector3 a_right) :
            base(a_right, a_normal)
        {
            Name = "Plane";
            m_uv_mapper = new PlaneUVMapper();
            Update(UpdateFlags.All);
        }

        public PlaneObject(Vector3 a_v1, Vector3 a_v2, Vector3 a_v3)
            : base(Vector3.CrossProduct(a_v2 - a_v1, a_v3 - a_v1).Normalized,
                    a_v2 - a_v1)
        {
            Name = "Plane";
            m_uv_mapper = new PlaneUVMapper();
            Pos = a_v1;
            Update(UpdateFlags.All);
        }

        public double Distance
        {
            get
            {
                return Plane.GetDist(Normal, Pos, Vector3.ZERO);
            }
        }

        public Vector3 Normal
        {
            get
            {
                return Up;
            }
        }

        internal override Intersection GetIntersection(Intersection a_source_ray_intersection, Ray a_ray)
        {
            Vector3 local_start;
            Vector3 local_dir;
            TransformToLocal(a_ray, out local_dir, out local_start);

            if (OneSide)
            {
                if (local_dir.Y > 0)
                    return Scene.NoIntersection;
            }

            if ((a_source_ray_intersection != null) && (a_source_ray_intersection.SceneObject == this))
                return Scene.NoIntersection;

            if (local_dir.Y.IsAlmostRelativeEquals(0))
                return Scene.NoIntersection;
 
            double dist = local_start.Y / -local_dir.Y;

            if (dist <= 0)
                return Scene.NoIntersection;
            else
            {
                bool back_hit = (local_dir.Y > 0);

                if (back_hit && OneSide)
                    return Scene.NoIntersection;
                else
                {
                    Vector3 local_pos = local_start + local_dir * dist;
                    Vector3 world_pos = LocalToWorld * local_pos;
                    dist = (world_pos - a_ray.Start).Length;

                    return new Intersection()
                    {
                        PrevIntersection = a_source_ray_intersection, 
                        SceneObject = this,
                        SourceRay = a_ray,
                        Dist = dist,
                        Scene = Scene,
                        BackHit = back_hit,
                        Pos = world_pos,
                        LocalPos = local_pos
                    };
                }
            }    
        }

        internal override Vector3 GetNormal(Intersection a_intersection)
        {
            if (a_intersection.BackHit)
                return -Normal;
            else
                return Normal;
        }

        public override string ToString()
        {
            return String.Format("Plane: {0}", Name);
        }

        internal override Vector2 GetUV(Intersection a_intersection)
        {
            Debug.Assert(a_intersection.UVW.Y.IsAlmostRelativeEquals(0));
            return new Vector2(a_intersection.UVW.X, a_intersection.UVW.Z);
        }

        internal override void GetTangents(Intersection a_intersection,
            out Vector3 a_tangent_x, out Vector3 a_tangent_y)
        {
            a_tangent_x = Right;
            a_tangent_y = Forward;
        }

        protected override AABB GetLocalBoundBox()
        {
            return new AABB(new Vector3(Constants.DOUBLE_MIN, Constants.DOUBLE_MIN, -Constants.MINIMAL_DISTANT),
                            new Vector3(Constants.DOUBLE_MAX, Constants.DOUBLE_MAX, Constants.MINIMAL_DISTANT));
        }

        public override void ScaleAbsolute(double a_scale)
        {
            Scale *= a_scale;

            base.ScaleAbsolute(a_scale);
        }
    }
}